package com.easipass.framework.eirframeworkcoredsl;

import java.util.*;

public class AppParser extends ServiceParser {

    private final String dsl;

    private final boolean validate;

    public AppParser(String dsl) {
        this(null, dsl, false);
    }

    public AppParser(String name,String dsl) {
        this(name, dsl, false);
    }

    public AppParser(String dsl, boolean validate) {
        this(null, dsl, validate);
    }

    public AppParser(String name, String dsl, boolean validate) {
        super(new Tokenizer().getTokens(dsl),name);
        this.dsl = dsl;
        this.validate = validate;
    }

    public AppNode parse() {
        AppNode appNode = eatApp();

        if (appNode.getName() != null && !isValidName(appNode.getName())) {
            throw new ParseException(appNode.getName(), 0, DSLMessage.ILLEGAL_APP_NAME, appNode.getName());
        }

        if (getTokens().hasNext()) {
            getTokens().raiseException(getTokens().peek().startPos, DSLMessage.SERVICE_MORE_INPUT,
                    toString(getTokens().next()));
        }

        if (validate) {
            AppValidatorVisitor validator = new AppValidatorVisitor();
            appNode.accept(validator);
            List<AppValidationProblem> problems = validator.getProblems();
            if (!problems.isEmpty()) {
                throw new AppValidationException(appNode, problems);
            }
        }

        return appNode;
    }

    private String eatAppName() {
        Tokens tokens = getTokens();
        String appName = super.appName;
        if (tokens.lookAhead(1, TokenKind.EQUALS)) {
            if (tokens.peek(TokenKind.IDENTIFIER)) {
                appName = tokens.eat(TokenKind.IDENTIFIER).data;
                tokens.next();
            } else {
                tokens.raiseException(tokens.peek().startPos, DSLMessage.ILLEGAL_APP_NAME, toString(tokens.peek()));
            }
        }
        return appName;
    }

    private AppNode eatApp() {
        this.appName = eatAppName();
        SourceDestinationNode sourceDestinationNode = eatSourceDestination();
        List<LabelledServiceNode> serviceNodes = new ArrayList<>();
        serviceNodes.addAll(eatFunctionList());
        SinkDestinationNode sinkDestinationNode = eatSinkDestination();
        return new AppNode(appName, dsl, serviceNodes, sourceDestinationNode, sinkDestinationNode);
    }

    private boolean noMorePipes() {
        return noMorePipes(getTokens().position());
    }

    private boolean noMorePipes(int position) {
        List<Token> tokenList = getTokens().getTokenService();
        int tokenStreamLength = tokenList.size();
        while (position < tokenStreamLength) {
            if (tokenList.get(position++).getKind() == TokenKind.PIPE) {
                return false;
            }
        }
        return true;
    }

    private boolean looksLikeDestination() {
        return looksLikeDestination(getTokens().position());
    }

    private boolean looksLikeDestination(int position) {
        Tokens tokens = getTokens();
        List<Token> tokenList = tokens.getTokenService();
        if (tokens.hasNext() && tokenList.get(position).getKind() == TokenKind.COLON) {
            if (tokenList.get(position - 1).isKind(TokenKind.GT)) {
                return true;
            }
        }
        return false;
    }

    private SourceDestinationNode eatSourceDestination() {
        Tokens tokens = getTokens();
        boolean gtBeforePipe = false;
        List<Token> tokenList = tokens.getTokenService();
        for (int i = tokens.position(); i < tokenList.size(); i++) {
            Token t = tokenList.get(i);
            if (t.getKind() == TokenKind.GT) {
                gtBeforePipe = true;
                break;
            } else if (t.getKind() == TokenKind.PIPE) {
                break;
            }
        }
        if (!gtBeforePipe) {
            return null;
        }

        DestinationNode destinationNode = eatDestinationReference();
        if (destinationNode == null) {
            return null;
        }
        Token gt = tokens.eat(TokenKind.GT);
        return new SourceDestinationNode(destinationNode, gt.endPos);
    }

    private SinkDestinationNode eatSinkDestination() {
        Tokens tokens = getTokens();
        SinkDestinationNode SinkDestinationNode = null;
        if (tokens.peek(TokenKind.GT)) {
            Token gt = tokens.eat(TokenKind.GT);
            DestinationNode destinationNode = eatDestinationReference();
            if (destinationNode == null) {
                return null;
            }
            SinkDestinationNode = new SinkDestinationNode(destinationNode, gt.startPos);
        }
        return SinkDestinationNode;
    }

    private Token peekDestinationComponentToken() {
        Token t = getTokens().peek();
        if (t == null) {
            return null;
        }
        if (t.kind == TokenKind.IDENTIFIER || t.kind == TokenKind.STAR || t.kind == TokenKind.SLASH
                || t.kind == TokenKind.HASH) {
            return t;
        } else {
            return null;
        }
    }

    private String getTokenData(Token token) {
        return token.kind.hasPayload() ? token.data : new String(token.kind.getTokenChars());
    }


    private List<LabelledServiceNode> eatFunctionList() {
        List<LabelledServiceNode> sequence = new ArrayList<>();
        sequence.add(new FunctionNode(eatServiceList()));
        while (maybeEat(TokenKind.PIPE) ) {
            sequence.add(new FunctionNode(eatServiceList()));
        }
        return sequence;
    }

    private List<LabelledServiceNode> eatServiceList() {
        List<LabelledServiceNode> sequence = new ArrayList<>();
        sequence.add(parseServiceNode());
        while (getTokens().hasNext() && (nextTokenIsOnNewline() || maybeEat(TokenKind.SEMICOLON))) {
            sequence.add(parseServiceNode());
        }
        return sequence;
    }

    @Override
    public String toString() {
        Tokens tokens = getTokens();
        return String.valueOf(tokens.getTokenService()) + "\n" + "tokenStreamPointer=" + tokens.position() + "\n";
    }

    private DestinationNode eatDestinationReference() {
        Tokens tokens = getTokens();
        Token firstToken = tokens.next();
        if (!firstToken.isKind(TokenKind.COLON)) {
            tokens.decrementPosition();
            return null;
        }
        StringBuilder destinationName = new StringBuilder();
        Token token = peekDestinationComponentToken();
        if (token == null) {
            if (tokens.peek() == null) {
                tokens.raiseException(firstToken.startPos, DSLMessage.OOD);
            } else {
                tokens.raiseException(tokens.peek().startPos, DSLMessage.UNEXPECTED_DATA_IN_DESTINATION_NAME,
                        getTokenData(tokens.peek()));
            }
        }
        int startpos = token.startPos;
        destinationName.append(getTokenData(token));
        tokens.next();
        while (tokens.isNextAdjacent() && (token = peekDestinationComponentToken()) != null) {
            destinationName.append(getTokenData(token));
            tokens.next();
        }
        while (tokens.peek(TokenKind.DOT)) {
            if (!tokens.isNextAdjacent()) {
                tokens.raiseException(tokens.peek().startPos, DSLMessage.NO_WHITESPACE_IN_DESTINATION_DEFINITION);
            }
            tokens.next();
            destinationName.append(TokenKind.DOT.getTokenChars());
            if (!tokens.isNextAdjacent()) {
                tokens.raiseException(tokens.peek().startPos, DSLMessage.NO_WHITESPACE_IN_DESTINATION_DEFINITION);
            }
            while (tokens.isNextAdjacent() && (token = peekDestinationComponentToken()) != null) {
                destinationName.append(getTokenData(token));
                tokens.next();
            }
        }
        int endPos = token.endPos;
        ArgumentNode[] argumentNodes = eatAppArgs();
        return new DestinationNode(startpos, endPos, destinationName.toString(), argumentNodes);
    }

}
